/*
 * Generic receive layer for the PXA USB client function
 *
 * This code was loosely inspired by the original version which was
 * Copyright (c) Compaq Computer Corporation, 1998-1999
 * Copyright (c) 2001 by Nicolas Pitre
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 02-May-2002
 *   Frank Becker (Intrinsyc) - derived from sa1100 usb_recv.c
 * 
 * TODO: Add support for DMA.
 *
 */

#ifdef HAVE_CONFIG_H
# include <blob/config.h>
#endif

#include <blob/arch.h>

#include <linux/errno.h>
#include "pxa_usb.h"
#include "usb_ctl_pxa.h"

#if DEBUG
static unsigned int usb_debug = DEBUG;
#else
#define usb_debug 0     /* gcc will remove all the debug code for us */
#endif

static char *ep2_buf;
static int   ep2_len;
static int   ep2_remain;
static usb_callback_t ep2_callback;
static int rx_pktsize;

static void
ep2_start(void)
{
	/* disable DMA, RPC & SST should not be cleared */
	UDCCS2 &= ~( UDCCS_BO_DME | UDCCS_BO_RPC | UDCCS_BO_SST );

	/* enable interrupts for endpoint 2 (bulk out) */
        UICR0 &= ~UICR0_IM2;
}

static void
ep2_done(int flag)
{
	int size = ep2_len - ep2_remain;

	if (!ep2_len)
		return;
	//SerialOutputHex("done\n");

	ep2_len = 0;
	if (ep2_callback) {
		ep2_callback(flag, size);
	}
}

void
ep2_state_change_notify( int new_state )
{
}

void
ep2_stall( void )
{
	/* SET_FEATURE force stall at UDC */
	UDCCS2 |= UDCCS_BO_FST;
}

int
ep2_init(int chn)
{
	/*
	int i;
	desc_t * pdesc = pxa_usb_get_descriptor_ptr();

	for ( i = 0; i < pdesc->ep_num; i++ ) {
		if( BULK_OUT1 == ( 0xF & pdesc->ep[i].bEndpointAddress) ) {
			rx_pktsize = __le16_to_cpu( pdesc->ep[i].wMaxPacketSize );
		}
	}
	*/

	/* FIXME */
	rx_pktsize = 64;
	ep2_done(-EAGAIN);
	return 0;
}

void
ep2_reset(void)
{
	/*
	int i;
	desc_t * pdesc = pxa_usb_get_descriptor_ptr();

	for ( i = 0; i < pdesc->ep_num; i++ ) {
		if( BULK_OUT1 == ( 0xF & pdesc->ep[i].bEndpointAddress) ) {
			rx_pktsize = __le16_to_cpu( pdesc->ep[i].wMaxPacketSize );
		}
	}
	*/
	/* FIXME */
	rx_pktsize = 64;
	UDCCS2 &= ~UDCCS_BO_FST;
	ep2_done(-EINTR);
}

void
ep2_int_hndlr(int udcsr)
{
	int status = UDCCS2;
	//if( usb_debug) printk("ep2_int_hndlr: UDCCS2=%x\n", status);

	if( (status & (UDCCS_BO_RNE | UDCCS_BO_RSP)) == UDCCS_BO_RSP)
	{
		/* zero-length packet */
	}

	if( status & UDCCS_BO_RNE)
	{
		int len;
		int i;
		char *buf = ep2_buf + ep2_len - ep2_remain;

		/* bytes in FIFO */
		len = (UBCR2 & 0xff) +1;
		
		/*
		if( usb_debug) printk("usb_recv: "
			"len=%d out1_len=%d out1_remain=%d\n",
			len,ep2_len,ep2_remain);
		*/

		if( len > ep2_remain)
		{
			/* FIXME: if this happens, we need a temporary overflow buffer */
			//printk("usb_recv: Buffer overwrite warning...\n");
			len = ep2_remain;
		}

		/* read data out of fifo */
		for( i=0; i<len; i++)
		{
			*buf++ = UDDR2 & 0xff;
		}

		ep2_remain -= len;
		ep2_done((len) ? 0 : -EPIPE);
	}

	/* ack RPC - FIXME: '|=' we may ack SST here, too */
	UDCCS2 |= UDCCS_BO_RPC;

	return;
}

int
ep2_recv(char *buf, int len, usb_callback_t callback)
{
	int flags;

	if (ep2_len)
		return -EBUSY;

	ep2_buf = buf;
	ep2_len = len;
	ep2_callback = callback;
	ep2_remain = len;
	ep2_start();

	return 0;
}

